package com.uxsino.commons.utils;

import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

public class RSAUtils {

    public static final String CHARSET = "UTF-8";

    public static final String RSA_ALGORITHM = "RSA";

    private static String decodeprovider = "SunJCE";

    public static final String DEFAULT_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnCP4x5GfUeKEjCf7b5k8S7DPbc7db6YZbkXKRMBkbgF/VIjgdCv6rwnGhCHc/JwVXF8ui6ozyQq5AfJrjQynK/139hfyM+7ob3lVQz6dZiYI3BtTbQMSXD58IYVQ7SeijWmnGQ/EOP4R4FeLEsxAmoLw/xoNeErfNplmM8nxg+wIDAQAB";

    public static final String DEFAULT_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKcI/jHkZ9R4oSMJ/tvmTxLsM9tzt1vphluRcpEwGRuAX9UiOB0K/qvCcaEIdz8nBVcXy6LqjPJCrkB8muNDKcr/Xf2F/Iz7uhveVVDPp1mJgjcG1NtAxJcPnwhhVDtJ6KNaacZD8Q4/hHgV4sSzECagvD/Gg14St82mWYzyfGD7AgMBAAECgYBzB1JmcXC/dUMiqiOEtBoIYflkS1AeT7djH8rSCVmBAur8D9Csz4GKLyGpT/cu2mRnEc/+pZ/ixeDSL/1KPFpugBF9XS37xU/mWaCZkQW3Vv5CMTgCidd99IcELPCWi4tcvi8JvbgFyfD7YVrE3IbP0A1jKVKoEkjw+Vy98jAnYQJBANui/CvgpZDSmuZ4c/IeQalHGhBsVaxlqL6l0CI9+Qtpkkd/qqN0KfyxJKdp/yY46SsJGYlTJLARi89YfjLR3ScCQQDCsI9qVdimq0QyysLyAUwrhtHRzGZwlCkUqR+hDcDZFqCp7wgny4jV/kqXng9Q68WOCc2ZtCzserigW/ny7moNAkAIct+SbITR6lgND4seQC20bkjBYvl0tUBlfQlhl9KxrrDy0SVmPjyzPLk1O22WMdA+WI5D6w/ZOgOPyU6pc8s3AkEAg9nPRtScxUN4DG4FWKBIVbIYp10mg1fT+ooPQaNu15bbeDrJ6ymMomB1Y/lHpiYGxQsektnTEtSJ9RBVVpDggQJAKZyEtzPJYfOwgBAJN/JgcvBiiuPN8DcUJa4YxmIfESf7QPm87JJCnEzc3oNMtIqdEv4r4mWrO2VDvnEBTugveQ==";

    private static RSAPublicKey defaultPublicKey = null;

    private static RSAPrivateKey defaultPrivateKey = null;

    /**
     * 生成密匙对
     * @param keySize
     * @return
     */
    public static Map<String, String> createKeys(int keySize) {
        // 为RSA算法创建一个KeyPairGenerator对象
        KeyPairGenerator kpg;
        try {
            kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("No such algorithm-->[" + RSA_ALGORITHM + "]", e);
        }

        // 初始化KeyPairGenerator对象,密钥长度
        kpg.initialize(keySize);
        // 生成密匙对
        KeyPair keyPair = kpg.generateKeyPair();
        // 得到公钥
        Key publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
        // 得到私钥
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
        Map<String, String> keyPairMap = new HashMap<String, String>();
        keyPairMap.put("publicKey", publicKeyStr);
        keyPairMap.put("privateKey", privateKeyStr);

        return keyPairMap;
    }

    /**
     * 得到公钥
     * @param publicKey 密钥字符串（经过base64编码）
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 通过X509编码的Key指令获得公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
        RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
        return key;
    }

    /**
     * 得到私钥
     * @param privateKey 密钥字符串（经过base64编码）
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String privateKey)
        throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 通过PKCS#8编码的Key指令获得私钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
        RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
        return key;
    }

    /**
     * 公钥加密
     * @param data
     * @param publicKey
     * @return
     */
    public static String publicEncrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM,decodeprovider);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.encodeBase64URLSafeString(
                rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥解密
     * @param data
     * @param privateKey
     * @return
     */

    public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM, decodeprovider);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data.getBytes(CHARSET)),
                privateKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥加密
     * @param data
     * @param privateKey
     * @return
     */

    public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET),
                privateKey.getModulus().bitLength()));
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 公钥解密
     * @param data
     * @param publicKey
     * @return
     */

    public static String publicDecrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data),
                publicKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 分段解密
     * @param cipher
     * @param opmode
     * @param datas
     * @param keySize
     * @return
     */
    private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
        int maxBlock = 0;
        if (opmode == Cipher.DECRYPT_MODE) {
            maxBlock = keySize / 8;
        } else {
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        try {
            while (datas.length > offSet) {
                if (datas.length - offSet > maxBlock) {
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                } else {
                    buff = cipher.doFinal(datas, offSet, datas.length - offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
        } catch (Exception e) {
            throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
        }
        byte[] resultDatas = out.toByteArray();
        IOUtils.closeQuietly(out);
        return resultDatas;
    }

    /**
     * 默认公钥加密
     * @param data
     * @return
     * @throws InvalidKeySpecException 
     * @throws NoSuchAlgorithmException 
     */
    public static String publicEncrypt(String data) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (data == null || "".equals(data)) {
            return data;
        }
        return publicEncrypt(data, defaultPublicKey == null ? getPublicKey(DEFAULT_PUBLIC_KEY) : defaultPublicKey);
    }

    /**
     * 默认私钥解密
     * @param data
     * @return
     * @throws InvalidKeySpecException 
     * @throws NoSuchAlgorithmException 
     */

    public static String privateDecrypt(String data) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (data == null || "".equals(data)) {
            return data;
        }
        return privateDecrypt(data, defaultPrivateKey == null ? getPrivateKey(DEFAULT_PRIVATE_KEY) : defaultPrivateKey);
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Map<String, String> newKeys = RSAUtils.createKeys(1024);
        String pubKey = newKeys.get("publicKey");
        System.out.println("公钥：" + pubKey);
        String priKey = newKeys.get("privateKey");
        System.out.println("私钥：" + priKey);
        String pwd = "admin123";
        String encodeStr = publicEncrypt(pwd, RSAUtils.getPublicKey(pubKey));
        System.out.println("公钥加密：" + encodeStr);
        System.out.println("私钥解密：" + privateDecrypt(encodeStr, RSAUtils.getPrivateKey(priKey)));
    }

}